import * as MethodName from './MethodName';

import { pen, writeCodeHandle, receiveOfflineData, receiveOnlineData } from "./pen"

//发布订阅模式
const event = require('./event')

receiveOfflineData((dot) => {
    event.emit("AddressDataChanged", dot)
    // console.log("离线", dot)
})
receiveOnlineData((dot) => {
    // console.log("在线", dot)
    event.emit("AddressDataChanged", dot)
})
//蓝牙服务
const SERVICE = ['0000f100-0000-1000-8000-00805f9b34fb', '0000f200-0000-1000-8000-00805f9b34fb', '0000fff0-0000-1000-8000-00805f9b34fb']
//发送命令 特征值
const CHARACTERISTIC_SEND = ['0000f102-0000-1000-8000-00805f9b34fb', '0000f201-0000-1000-8000-00805f9b34fb', '0000fff2-0000-1000-8000-00805f9b34fb']
//接收数据 特征值
const CHARACTERISTIC_RECEIVE = ['0000f101-0000-1000-8000-00805f9b34fb', '0000f202-0000-1000-8000-00805f9b34fb', '0000fff1-0000-1000-8000-00805f9b34fb']

//笔类型
const PEN_TYPE = ['TQL-101', 'TQL-111', 'TQL-112', 'TQL-101A', '', 'PE14-M01', '白板笔 WB-101', 'PT14-WF101', '130-T41(PT14-T101)', '130-T31(OID3S镜头)']

//工具类
class Utils {
    /**
     * @name 获取信息命令
     * @param {object} characteristic 特征值
     * @param {number[]} array 十进制数组 
     */
    sendCommand(characteristic, array) {
        characteristic.writeValue(new DataView(new Uint8Array(array).buffer))
    }
    /**
     * @name 十进制转十六进制
     * @param {number} dec 十进制数
     * @returns {string}
     */
    decToHex(dec) {
        return dec <= 0xf ? `0${dec.toString(16)}` : dec.toString(16)
    }
    /**
     * @name 字节数组转字符
     * @param {Array<number>} byteArray 
     * @returns 
     */
    byteArrayToString(byteArray) {
        let str = ''
        for (let i = 0; i < byteArray.length; i++) {
            let bin = byteArray[i].toString(2)
            let one = bin.match(/^1+?(?=0)/)
            if (one && bin.length === 8) {
                let byteLength = one[0].length
                let store = byteArray[i].toString(2).slice(7 - byteLength)
                for (let j = 1; j < byteLength; j++) {
                    store += byteArray[i + j].toString(2).slice(2)
                }
                str += String.fromCharCode(parseInt(store, 2))
                i += byteLength - 1
                continue
            }
            str += String.fromCharCode(byteArray[i])
        }
        return str
    }
    /**
     * @name 字符串转字节数组
     * @param {string} str 
     * @returns 
     */
    stringToByteArray(str) {
        const utf8 = [];
        for (let ii = 0; ii < str.length; ii++) {
            let charCode = str.charCodeAt(ii);
            if (charCode < 0x80) utf8.push(charCode);
            else if (charCode < 0x800) {
                utf8.push(0xc0 | (charCode >> 6), 0x80 | (charCode & 0x3f));
            } else if (charCode < 0xd800 || charCode >= 0xe000) {
                utf8.push(0xe0 | (charCode >> 12), 0x80 | ((charCode >> 6) & 0x3f), 0x80 | (charCode & 0x3f));
            } else {
                ii++;
                charCode = 0x10000 + (((charCode & 0x3ff) << 10) | (str.charCodeAt(ii) & 0x3ff));
                utf8.push(
                    0xf0 | (charCode >> 18),
                    0x80 | ((charCode >> 12) & 0x3f),
                    0x80 | ((charCode >> 6) & 0x3f),
                    0x80 | (charCode & 0x3f),
                );
            }
        }
        return utf8;
    }
    //发布 penData 
    emitPenData(operation, data) {
        event.emit('penData', {
            operation,
            ...data
        })
    }
}

/**
 * @name WebBluetooth封装
 * @author 陈德
 * @date  2021-11-26
 */
class WebBluetooth extends Utils {
    constructor() {
        super()
        this.isMobile = document.body.clientWidth < 900
        this.device = null
        this.F102 = null
        this.F201 = null
        this.FFF2 = null
        this.F101 = null
        this.F202 = null
        this.FFF1 = null
    }
    /**
     * @name 搜索蓝牙并连接笔
     * @param {function} success 连接成功回调函数
     * @param {function} fail  连接失败回调函数
     */
    async connect() {
        navigator.bluetooth.requestDevice({
            filters: [{
                manufacturerData: [{
                    companyIdentifier: 0x3231,
                }]
            }],
            optionalServices: [
                "0000fff0-0000-1000-8000-00805f9b34fb",
                "0000f200-0000-1000-8000-00805f9b34fb",
                "0000f100-0000-1000-8000-00805f9b34fb",
                "9e5d1e47-5c13-43a0-8635-82ad38a1386f"
            ],
        }).then(async device => {
            //切换笔时先断开前面的笔再连接下一支笔
            if (this.device !== null) this.disconnect()
            this.getAllServices(device)
        }).catch(function (error) {
            console.log("出现错误： " + error)
        })
    }
    //获取所有服务
    async getAllServices(device) {
        this.device = device
        //只有移动端触发
        if (this.isMobile) this.emitPenData(MethodName.A0, { name: device.name })
        const server = await device.gatt.connect()
        //连接失败时触发
        if (!server.connected) event.emit('connect', false)
        device.addEventListener('gattserverdisconnected', e => event.emit('disconnect', e))
        //获取所有服务
        const services = await server.getPrimaryServices()
        services.forEach(async (i, iIndex) => {
            //获取单个服务
            const service = await server.getPrimaryService(i.uuid)
            //获取该服务中所有的特征值
            const characteristics = await service.getCharacteristics()
            characteristics.forEach(async (j, jIndex) => {
                const characteristic = await service.getCharacteristic(j.uuid)
                if (CHARACTERISTIC_RECEIVE.includes(j.uuid)) this.receiveCharacteristic(characteristic)
                if (CHARACTERISTIC_SEND.includes(j.uuid)) this.sendCharacteristic(characteristic)
                if (iIndex === services.length - 1 && jIndex === characteristics.length - 1) {
                    event.emit('connect', true)
                    this[MethodName.E4]()
                    this.getCodePointValue()
                }
            })
        })
    }
    //断开连接
    disconnect() {
        this.device.gatt.disconnect()
        this.device = null
        this.F102 = null
        this.F201 = null
        this.FFF2 = null
        this.F101.removeEventListener('characteristicvaluechanged', this.watchF101)
        this.F202.removeEventListener('characteristicvaluechanged', this.watchF202)
        this.FFF1.removeEventListener('characteristicvaluechanged', this.watchFFF1)
        this.F101 = null
        this.F202 = null
        this.FFF1 = null
    }
    //发送数据的特征值
    sendCharacteristic(characteristic) {
        let index = CHARACTERISTIC_SEND.indexOf(characteristic.uuid)
        if (index === 0) this.F102 = characteristic
        if (index === 1) this.F201 = characteristic
        if (index === 2) this.FFF2 = characteristic
    }
    //接收数据的特征值 并监听
    receiveCharacteristic(characteristic) {
        // await characteristic.readValue()
        let index = CHARACTERISTIC_RECEIVE.indexOf(characteristic.uuid)
        if (index === 0) {
            this.F101 = characteristic
            this.F101.addEventListener('characteristicvaluechanged', e => this.watchF101(e))
            if (!this.isMobile) this.startF101Notifications()
        }
        if (index === 1) {
            this.F202 = characteristic
            this.F202.addEventListener('characteristicvaluechanged', e => this.watchF202(e))
            if (!this.isMobile) this.startF202Notifications()
        }
        if (index === 2) {
            this.FFF1 = characteristic
            this.FFF1.addEventListener('characteristicvaluechanged', e => this.watchFFF1(e))
            this.startFFF1Notifications()
        }
    }
    //开启通知
    startF101Notifications() {
        this.F101.startNotifications()
    }
    startF202Notifications() {
        this.F202.startNotifications()
    }
    startFFF1Notifications() {
        this.FFF1.startNotifications()
    }
    //监听F101 笔信息
    watchF101(e) {
        let data = new Uint8Array(e.currentTarget.value.buffer)
        switch (data[0]) {
            case 0x00: {
                this.emitPenData(MethodName.F0400, { isSuccess: !Boolean(data[5]) })
                break;
            }
            case 0xA1: { //获取蓝牙设备名
                const name = this.byteArrayToString([...data.slice(2)])
                this.emitPenData(MethodName.A0, { name })
                break;
            }
            case 0xA3: //设置蓝牙设备名
                this.emitPenData(MethodName.A2, { isSuccess: !Boolean(data[2]) })
                break;
            case 0xA5: { //蓝牙MAC
                const mac = [...data.slice(2)].map(i => this.decToHex(i)).join(':').toUpperCase()
                this.emitPenData(MethodName.A4, { mac })
                break;
            }
            case 0xA7: { //蓝牙固件版本
                const version = String.fromCharCode(...[...data.slice(2)])
                this.emitPenData(MethodName.A6, { version })
                break;
            }
            case 0xA9: { //获取电量
                const electricQuantity = `${data[2]}%` //电量
                const isCharging = !Boolean(data[3]) //是否在充电
                this.emitPenData(MethodName.A8, { electricQuantity, isCharging })
                break;
            }
            case 0xAB: { //获取RTC时间
                const startTime = 1262275200000
                const penTime = parseInt([...data.slice(2)].reverse().map(i => this.decToHex(i)).join(''), 16) * 1000
                const timestamp = startTime + penTime
                this.emitPenData(MethodName.AA, { timestamp })
                break;
            }
            case 0xAD: //设置RTC时间 - 返回值
                this.emitPenData(MethodName.AC, { isSuccess: !Boolean(data[2]) })
                break;
            case 0xB1: { //获取当前笔的自动关机时间
                const time = parseInt([...data.slice(2)].reverse().map(i => this.decToHex(i)).join(''), 16)
                const unit = 'min'
                this.emitPenData(MethodName.B0, { time, unit })
                break;
            }
            case 0xB3: //设置自动关机时间 - 返回值
                this.emitPenData(MethodName.B2, { isSuccess: !Boolean(data[2]) })
                break;
            case 0xB5: //出厂设置 - 返回值
                this.emitPenData(MethodName.B4, { isSuccess: !Boolean(data[2]) })
                break;
            case 0xB7: { //获取目前Flash内存占有百分比
                const proportion = `${data[2]}%`
                this.emitPenData(MethodName.B6, { proportion })
                break;
            }
            case 0xB9: //获取笔尖压力唤醒
                this.emitPenData(MethodName.B8, { isOpen: Boolean(data[2]) })
                break;
            case 0xBB: //设置笔尖压力唤醒 - 返回值
                this.emitPenData(MethodName.BA, { isSuccess: !Boolean(data[2]) })
                break;
            case 0xBD: //获取蜂鸣器是否开启
                this.emitPenData(MethodName.BC, { isOpen: Boolean(data[2]) })
                break;
            case 0xBF: //设置蜂鸣器是否开启 - 返回值
                this.emitPenData(MethodName.BE, { isSuccess: !Boolean(data[2]) })
                break;
            case 0xC1: { //获取离线数据量
                const size = parseInt([...data.slice(2)].reverse().map(i => this.decToHex(i)).join(''), 16)
                const unit = 'bytes'
                this.emitPenData(MethodName.C0, { size, unit })
                break;
            }
            case 0xC3: //开始获取离线数据
                this.emitPenData(MethodName.C2_0, { isSuccess: !Boolean(data[2]) })
                break;
            case 0xC4: //停止获取离线数据
                this.emitPenData(MethodName.C2_1, { isSuccess: !Boolean(data[2]) })
                break;
            case 0xC5: //离线传输结束 - 返回值
                this.emitPenData(MethodName.C5, { isSuccess: !Boolean(data[4]) })
                break;
            case 0xC9: //删除离线 - 返回值
                this.emitPenData(MethodName.C8, { isSuccess: !Boolean(data[2]) })
                break;
            case 0xCC: //暂停离线 -返回值
                this.emitPenData(MethodName.CB_0, { isSuccess: !Boolean(data[2]) })
                break;
            case 0xCD: //继续离线 -返回值
                this.emitPenData(MethodName.CB_1, { isSuccess: !Boolean(data[2]) })
                break;
            case 0xD1: //获取压力sensor敏感度
                this.emitPenData(MethodName.D0, { sensor: data[2] })
                break;
            case 0xD3: //设置压力sensor敏感度 - 返回值
                this.emitPenData(MethodName.D2, { isSuccess: !Boolean(data[2]) })
                break;
            case 0xD5: //获取呼吸灯颜色
                this.emitPenData(MethodName.D4, { color: data[2] })
                break;
            case 0xD7: //设置呼吸灯颜色
                this.emitPenData(MethodName.D6, { isSuccess: !Boolean(data[2]) })
                break;
            case 0xD9: { //获取压力校正值
                const hexArray = [...data.slice(2)].reverse().map(i => this.decToHex(i))
                const pressure_20 = parseInt(`${hexArray[2]}${hexArray[3]}`, 16)
                const pressure_300 = parseInt(`${hexArray[0]}${hexArray[1]}`, 16)
                this.emitPenData(MethodName.D8, { pressure_20, pressure_300 })
                break;
            }
            case 0xDB: { //获取MCU版本
                const version = `MCUF_${data[2] ? 'R' : 'B'}${data[2] ? data[4] * 10 : data[4]}${data[5]}${data[6]}`
                this.emitPenData(MethodName.DA, { version })
                break;
            }
            case 0xDD: //获取CustomerId
                this.emitPenData(MethodName.DC, { data })
                break;
            case 0xDF: //设置CustomerId - 返回值
                this.emitPenData(MethodName.DE, { isSuccess: !Boolean(data[2]) })
                break;
            case 0xE1: //OID点读码
                console.log(data);
                break;
            case 0xE5: //查询笔的型号
                pen.penType = data[2]
                console.log("笔的型号",pen)
                this.emitPenData(MethodName.E4, { type: PEN_TYPE[data[2]] })
                break;
            case 0xE7: { // 码点类型
                // 同步 码点类型
                pen.codePointValue = data[2]
                console.log("码点类型",pen)
                break;
            }

        }
    }
    //监听F202 笔离线数据
    watchF202(e) {
        const data = new Uint8Array(e.currentTarget.value.buffer)
        const dataLength = data.length / 10
        for (let i = 0; i < dataLength; i++) {
            writeCodeHandle([...data.slice(i * 10, (i + 1) * 10)], false)
        }
    }
    //监听FFF1 笔在线数据
    watchFFF1(e) {
        const data = new Uint8Array(e.currentTarget.value.buffer)
        const dataLength = data.length / 10
        for (let i = 0; i < dataLength; i++) {
            writeCodeHandle([...data.slice(i * 10, (i + 1) * 10)], true)
        }
    }
    //获取蓝牙设备名
    [MethodName.A0]() {
        this.sendCommand(this.F102, [0xA0, 0x01, 0x01])
    }
    //设置蓝牙设备名
    [MethodName.A2](name) {
        let array = this.stringToByteArray(name)
        this.sendCommand(this.F102, [0xA2, array.length, ...array])
    }
    //获取蓝牙MAC地址
    [MethodName.A4]() {
        this.sendCommand(this.F102, [0xA4, 0x01, 0xFF])
    }
    //获取蓝牙固件版本
    [MethodName.A6]() {
        this.sendCommand(this.F102, [0xA6, 0x01, 0xFF])
    }
    //获取电量
    [MethodName.A8]() {
        this.sendCommand(this.F102, [0xA8, 0x01, 0xFF])
    }
    //获取RTC
    [MethodName.AA]() {
        this.sendCommand(this.F102, [0xAA, 0x01, 0xFF])
    }
    //设置RTC
    [MethodName.AC]() {
        let str = ''
        let TimeHex = Math.round((new Date() - 1262275200000) / 1000).toString(16)
        let length = TimeHex.length / 2
        for (let i = 0; i < length; i++) {
            str += `${TimeHex.substring(i * 2, (i + 1) * 2)}${i === length - 1 ? "" : ","}`
        }
        const timeArr = str.split(',').reverse().map(i => parseInt(i, 16))
        this.sendCommand(this.F102, [0xAC, 0x04, ...timeArr])
    }
    //获取笔自动关机时间
    [MethodName.B0]() {
        this.sendCommand(this.F102, [0xB0, 0x01, 0xFF])
    }
    //设置笔自动关机时间
    [MethodName.B2](time) {
        this.sendCommand(this.F102, [0xB2, 0x01, time, 0x00])
    }
    //恢复出厂设置
    [MethodName.B4]() {
        this.sendCommand(this.F102, [0xB4, 0x01, 0xFF])
    }
    //获取当前Flash占比
    [MethodName.B6]() {
        this.sendCommand(this.F102, [0xB6, 0x01, 0xFF])
    }
    //获取笔尖压力唤醒
    [MethodName.B8]() {
        this.sendCommand(this.F102, [0xB8, 0x01, 0xFF])
    }
    //设置笔尖压力唤醒 / isOpen:Boolean
    [MethodName.BA](isOpen) {
        this.sendCommand(this.F102, [0xBA, 0x01, Number(isOpen)])
    }
    //获取蜂鸣器是否开启
    [MethodName.BC]() {
        this.sendCommand(this.F102, [0xBC, 0x01, 0xFF])
    }
    //设置蜂鸣器是否开启 / isOpen:Boolean
    [MethodName.BE](isOpen) {
        this.sendCommand(this.F102, [0xBE, 0x01, Number(isOpen)])
    }
    //获取离线数据量
    [MethodName.C0]() {
        this.sendCommand(this.F102, [0xC0, 0x01, 0xFF])
    }
    //开始获取离线数据
    [MethodName.C2_0]() {
        this.sendCommand(this.F102, [0xC2, 0x01, 0x00])
    }
    //停止获取离线传输
    [MethodName.C2_1]() {
        this.sendCommand(this.F102, [0xC2, 0x01, 0x01])
    }
    //清除离线数据
    [MethodName.C8]() {
        this.sendCommand(this.F102, [0xC8, 0x01, 0xFF])
    }
    //暂停离线传输
    [MethodName.CB_0]() {
        this.sendCommand(this.F102, [0xCB, 0x01, 0x00])
    }
    //继续离线
    [MethodName.CB_1]() {
        this.sendCommand(this.F102, [0xCB, 0x01, 0x01])
    }
    //获取压力sensor
    [MethodName.D0]() {
        this.sendCommand(this.F102, [0xD0, 0x01, 0xFF])
    }
    //设置压力sensor / sensitivity:0-4
    [MethodName.D2](sensitivity) {
        this.sendCommand(this.F102, [0xD2, 0x01, sensitivity])
    }
    //获取呼吸灯颜色
    [MethodName.D4]() {
        this.sendCommand(this.F102, [0xD4, 0x01, 0xFF])
    }
    //设置呼吸灯颜色
    [MethodName.D6](color) {
        this.sendCommand(this.F102, [0xD6, 0x01, color])
    }
    //获取压力校正值
    [MethodName.D8]() {
        this.sendCommand(this.F102, [0xD8, 0x01, 0xFF])
    }
    //获取MCU版本
    [MethodName.DA]() {
        this.sendCommand(this.F102, [0xDA, 0x01, 0xFF])
    }
    //获取客户id
    [MethodName.DC]() {
        this.sendCommand(this.F102, [0xDC, 0x01, 0xFF])
    }
    //设置客户id
    [MethodName.DE]() { }
    //获取笔型号
    [MethodName.E4]() {
        this.sendCommand(this.F102, [0xE4, 0x01, 0xFF])
    }
    //获取码点类型
    getCodePointValue() {
        this.sendCommand(this.F102, [0xE6, 0x01, 0xFF])
    }
    //设置蜂鸣器鸣叫
    [MethodName.F0400](number, interval) {
        let intervalHex = interval.toString(16)
        if (interval <= 0xF) intervalHex = `000${intervalHex}`
        if (interval > 0xF && interval <= 0xFF) intervalHex = `00${intervalHex}`
        if (interval > 0xFF && interval <= 0xFFF) intervalHex = `0${intervalHex}`
        this.sendCommand(this.F102, [0x00, 0x08, 0x04, 0x00, 0x03, number, parseInt(intervalHex.slice(0, 2), 16), parseInt(intervalHex.slice(2, 4), 16)])
    }
}

export default WebBluetooth